package com.example.demo1.modbus;
import com.serotonin.modbus4j.Modbus;
import com.serotonin.modbus4j.ModbusFactory;
import com.serotonin.modbus4j.ModbusMaster;
import com.serotonin.modbus4j.exception.ModbusInitException;
import com.serotonin.modbus4j.exception.ModbusTransportException;
import com.serotonin.modbus4j.msg.*;
import gnu.io.SerialPort;
import gnu.io.SerialPortEvent;
import gnu.io.SerialPortEventListener;
import lombok.extern.slf4j.Slf4j;
import org.bouncycastle.util.encoders.Hex;

import java.util.Arrays;


@Slf4j
public class ModbusAciss {
    public static final String ACSII_PIFF = ":";
    public static final String ACSII_MODEL = ACSII_PIFF + "%s%s\r\n";
    public static void main(String[] args) throws InterruptedException {
        String comment = "010101050001";
//        "010101050001"
        String lrc = getLrc(comment);
        System.out.println(lrc);
        System.out.println(Integer.toHexString(':'));
        System.out.println(Hex.toHexString(":".getBytes()));
        System.out.println(Hex.toHexString((comment+lrc).toUpperCase().getBytes()));
        log.info(Hex.toHexString(":".getBytes()));
        log.info(Hex.toHexString("\r\n".getBytes()));
        String sendMsg = String.format(ACSII_MODEL, comment, lrc).toUpperCase();
        String msgHex = Hex.toHexString(sendMsg.getBytes());
        System.out.println(msgHex);

        ModbusFactory modbusFactory = new ModbusFactory();
        SerialPortWrapperImpl serialPortWrapper = new SerialPortWrapperImpl("COM3", 9600, SerialPort.DATABITS_7, SerialPort.STOPBITS_1, SerialPort.PARITY_EVEN,0,0);
//        modbusFactory.createAsciiMaster();
        ModbusMaster asciiMaster = modbusFactory.createAsciiMaster(serialPortWrapper);
//        modbusFactory.createRtuMaster()

        try {
            asciiMaster.setTimeout(200);
            asciiMaster.init();

//            readCoilTest(asciiMaster, 1, 2048, 5);
//            readDiscreteInputTest(asciiMaster, 1, 2048, 1);
            writeCoilTest(asciiMaster, 1, 1282, false);
        } catch (ModbusInitException e) {
//            e.printStackTrace();
            log.error("asciiMaster run is error, msg : {}", e.getMessage(), e);
        }finally {
            asciiMaster.destroy();
        }


    }

    /**
     * @Description: 读取开关量
     * @param master 主站实例
     * @param slaveId 从站ID
     * @param start 起始位
     * @param len 读取的长度
     */
    public static void readCoilTest(ModbusMaster master, int slaveId, int start, int len) {
        try {
            ReadCoilsRequest request = new ReadCoilsRequest(slaveId, start, len);
            ReadCoilsResponse response = (ReadCoilsResponse) master.send(request);

            if (response.isException())
                System.out.println("Exception response: message=" + response.getExceptionMessage());
            else
                System.out.println("功能码:1--" + Arrays.toString(response.getBooleanData()));
        }
        catch (ModbusTransportException e) {
            e.printStackTrace();
        }
    }

    /**
     * @Description: 读取外围设备输入的开关量
     * @param master 主站实例
     * @param slaveId 从站ID
     * @param start 起始位
     * @param len 长度
     */
    public static void readDiscreteInputTest(ModbusMaster master, int slaveId, int start, int len) {
        try {
            ReadDiscreteInputsRequest request = new ReadDiscreteInputsRequest(slaveId, start, len);
            ReadDiscreteInputsResponse response = (ReadDiscreteInputsResponse) master.send(request);

            if (response.isException())
                System.out.println("Exception response: message=" + response.getExceptionMessage());
            else
                System.out.println("功能码:2--" + Arrays.toString(response.getBooleanData()));
        }
        catch (ModbusTransportException e) {
            e.printStackTrace();
        }
    }

    /**
     * @Description: 读取保持寄存器数据
     * @param master 主站实例
     * @param slaveId 从站ID
     * @param start 起始位
     * @param len 长度
     */
    public static void readHoldingRegistersTest(ModbusMaster master, int slaveId, int start, int len) {
        try {
            ReadHoldingRegistersRequest request = new ReadHoldingRegistersRequest(slaveId, start, len);
            ReadHoldingRegistersResponse response = (ReadHoldingRegistersResponse) master.send(request);

            if (response.isException())
                System.out.println("Exception response: message=" + response.getExceptionMessage());
            else
                System.out.println("功能码:3--" + Arrays.toString(response.getShortData()));
        }
        catch (ModbusTransportException e) {
            e.printStackTrace();
        }
    }

    /**
     * @Description: 读取外围设备输入的数据
     * @param master 主站实例
     * @param slaveId 从站ID
     * @param start 起始位
     * @param len 长度
     */
    public static void readInputRegistersTest(ModbusMaster master, int slaveId, int start, int len) {
        try {
            ReadInputRegistersRequest request = new ReadInputRegistersRequest(slaveId, start, len);
            ReadInputRegistersResponse response = (ReadInputRegistersResponse) master.send(request);

            if (response.isException())
                System.out.println("Exception response: message=" + response.getExceptionMessage());
            else
                System.out.println("功能码:4--" + Arrays.toString(response.getShortData()));
        }
        catch (ModbusTransportException e) {
            e.printStackTrace();
        }
    }

    /**
     * @Description: 写开关量数据
     * @param master
     * @param slaveId 从站ID
     * @param offset 偏移量
     * @param value 写入的值
     */
    public static void writeCoilTest(ModbusMaster master, int slaveId, int offset, boolean value) {
        try {
            WriteCoilRequest request = new WriteCoilRequest(slaveId, offset, value);
            WriteCoilResponse response = (WriteCoilResponse) master.send(request);

            if (response.isException())
                System.out.println("Exception response: message=" + response.getExceptionMessage());
            else
                System.out.println("功能码:1,写入单个数据成功!");
        }
        catch (ModbusTransportException e) {
            e.printStackTrace();
        }
    }

    /**
     * @Description: 保持寄存器，写入单个数据
     * @param master
     * @param slaveId 从站ID
     * @param offset 偏移量
     * @param value
     */
    public static void writeRegisterTest(ModbusMaster master, int slaveId, int offset, int value) {
        try {
            WriteRegisterRequest request = new WriteRegisterRequest(slaveId, offset, value);
            WriteRegisterResponse response = (WriteRegisterResponse) master.send(request);

            if (response.isException())
                System.out.println("Exception response: message=" + response.getExceptionMessage());
            else
                System.out.println("功能码:3,写入单个模拟量数据成功!");
        }
        catch (ModbusTransportException e) {
            e.printStackTrace();
        }
    }

    /**
     * @Description: 读取异常状态
     * @param master
     * @param slaveId
     */
    public static void readExceptionStatusTest(ModbusMaster master, int slaveId) {
        try {
            ReadExceptionStatusRequest request = new ReadExceptionStatusRequest(slaveId);
            ReadExceptionStatusResponse response = (ReadExceptionStatusResponse) master.send(request);

            if (response.isException())
                System.out.println("Exception response: message=" + response.getExceptionMessage());
            else
                System.out.println(response.getExceptionStatus());
        }
        catch (ModbusTransportException e) {
            e.printStackTrace();
        }
    }

    public static void reportSlaveIdTest(ModbusMaster master, int slaveId) {
        try {
            ReportSlaveIdRequest request = new ReportSlaveIdRequest(slaveId);
            ReportSlaveIdResponse response = (ReportSlaveIdResponse) master.send(request);

            if (response.isException())
                System.out.println("Exception response: message=" + response.getExceptionMessage());
            else
                System.out.println(Arrays.toString(response.getData()));
        }
        catch (ModbusTransportException e) {
            e.printStackTrace();
        }
    }

    /**
     * @Description: 保持寄存器写多个数据
     * @param master
     * @param slaveId 从站ID
     * @param start 起始位置
     * @param values 数值
     */
    public static void writeCoilsTest(ModbusMaster master, int slaveId, int start, boolean[] values) {
        try {
            WriteCoilsRequest request = new WriteCoilsRequest(slaveId, start, values);
            WriteCoilsResponse response = (WriteCoilsResponse) master.send(request);

            if (response.isException())
                System.out.println("Exception response: message=" + response.getExceptionMessage());
            else
                System.out.println("功能码:1,写入多个数据成功!");
        }
        catch (ModbusTransportException e) {
            e.printStackTrace();
        }
    }

    /**
     * @Description: 保持寄存器写入多个模拟量数据
     * @param master
     * @param slaveId 从站ID
     * @param start modbus起始位置
     * @param values 数据
     */
    public static void writeRegistersTest(ModbusMaster master, int slaveId, int start, short[] values) {
        try {
            WriteRegistersRequest request = new WriteRegistersRequest(slaveId, start, values);
            WriteRegistersResponse response = (WriteRegistersResponse) master.send(request);

            if (response.isException())
                System.out.println("Exception response: message=" + response.getExceptionMessage());
            else
                System.out.println("功能码:3,写入多个模拟量数据成功!");
        }
        catch (ModbusTransportException e) {
            e.printStackTrace();
        }
    }

    public static void writeMaskRegisterTest(ModbusMaster master, int slaveId, int offset, int and, int or) {
        try {
            WriteMaskRegisterRequest request = new WriteMaskRegisterRequest(slaveId, offset, and, or);
            WriteMaskRegisterResponse response = (WriteMaskRegisterResponse) master.send(request);

            if (response.isException())
                System.out.println("Exception response: message=" + response.getExceptionMessage());
            else
                System.out.println("Success");
        } catch (ModbusTransportException e) {
            e.printStackTrace();
        }
    }


    public static void testRxtx(String msgHex){
        SerialPort serialPort = SerialPortUtil.openPort("COM3", 200, 9600, SerialPort.DATABITS_7, SerialPort.STOPBITS_1, SerialPort.PARITY_EVEN);
        System.out.println(serialPort);
        SerialPortUtil.addListener(serialPort, new SerialPortEventListener() {
            @Override
            public void serialEvent(SerialPortEvent serialPortEvent) {
                log.info("eventType : {}", serialPortEvent.getEventType());
                if (serialPortEvent.getEventType() == SerialPortEvent.DATA_AVAILABLE) {
                    //读取串口数据
                    byte[] bytes = SerialPortUtil.readFromPort(serialPort);
                    log.info("接收到的原始数据：" + BytesToHexUtil.bytesToHexString(bytes));
                }
            }
        });
//        Thread.sleep(2000);
        SerialPortUtil.sendToPort(serialPort, hexString2Bytes(msgHex));
    }

    /**
     * 16进制字符串转十进制字节数组
     * 这是常用的方法，如某些硬件的通信指令就是提供的16进制字符串，发送时需要转为字节数组再进行发送
     *
     * @param strSource 16进制字符串，如 "455A432F5600"，每两位对应字节数组中的一个10进制元素
     *                  默认会去除参数字符串中的空格，所以参数 "45 5A 43 2F 56 00" 也是可以的
     * @return 十进制字节数组, 如 [69, 90, 67, 47, 86, 0]
     */
    public static byte[] hexString2Bytes(String strSource) {
        if (strSource == null || "".equals(strSource.trim())) {
            System.out.println("hexString2Bytes 参数为空，放弃转换.");
            return null;
        }
        strSource = strSource.replace(" ", "");
        int l = strSource.length() / 2;
        byte[] ret = new byte[l];
        for (int i = 0; i < l; i++) {
            ret[i] = Integer.valueOf(strSource.substring(i * 2, i * 2 + 2), 16).byteValue();
        }
        return ret;
    }

//    private static void readHoldingRegistersTest(ModbusMaster master, int slaveId, int start, int len) {
//        try {
//            ReadHoldingRegistersRequest request = new ReadHoldingRegistersRequest(slaveId, start, len);
//            ReadHoldingRegistersResponse response = (ReadHoldingRegistersResponse)master.send(request);
//            if (response.isException()) {
//                System.out.println("Exception response: message=" + response.getExceptionMessage());
//            } else {
//                System.out.println(Arrays.toString(response.getShortData()));
//                short[] list = response.getShortData();
//                System.out.println("------------------------------------");
//                for (int i = 0; i < list.length; i++) {
//                    System.out.print(list[i] + " ");
//                }
//            }
//        } catch (ModbusTransportException e) {
//            e.printStackTrace();
//        }
//    }

    public static String getLrc(String comment){
        int sum = 0;
        char[] chars = comment.toUpperCase().toCharArray();
        for(int i=0; i<chars.length; i++){
            int i1 = hexToInt(chars[i]);
            if (i%2 == 0) {
                sum += i1 << 3;
            }else {
                sum += i1;
            }
        }
        int dec = 256 - sum % 256;
        return Integer.toHexString(dec).toUpperCase();
    }

    /**
     * 16进制转int
     * @param c
     * @return
     */
    public static int hexToInt(char c) {
        if (c <'A' || c >'F') {
            return Integer.valueOf(String.valueOf(c));
        }
        int num = 0;
        switch (c) {
            case 'A':
                num = 10;
                break;
            case 'B':
                num = 11;
                break;
            case 'C':
                num = 12;
                break;
            case 'D':
                num = 13;
                break;
            case 'E':
                num = 14;
                break;
            case 'F':
                num = 15;
                break;
            default:
                num = 0;
        }
        return num;
    }
}
